package com.ds.lens.data.service.metric;

import com.ds.lens.data.common.asserts.Asserts;
import com.ds.lens.data.common.client.vo.LensMetricInVO;
import com.ds.lens.data.common.constant.AggregatorEnum;
import com.ds.lens.data.common.constant.Constants;
import com.ds.lens.data.common.query.ESQueryAdaptor;
import com.ds.lens.data.common.query.MQLVariable;
import com.ds.lens.data.common.util.LensMQLBuilder;
import com.ds.lens.data.common.util.MapUtils;
import com.ds.lens.data.common.util.StartEndTimeRange;
import com.ds.lens.data.service.LensMetricsTagsService;
import com.ds.lens.data.vo.out.v2.integration.LensIntegrationDetailOutVO;
import com.ds.lens.data.vo.out.v2.metric.LensMetricNamesOutVO;
import com.ds.lens.data.vo.out.v2.monitor.LensMonitorFilterOutVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.stream.Collectors;

import static com.alibaba.druid.pool.DruidDataSourceFactory.PROP_CONNECTIONPROPERTIES;
import static com.alibaba.druid.pool.DruidDataSourceFactory.PROP_URL;

@Slf4j
@Service(Constants.METRIC_SERVICE_PREFIX + Constants.METRICS)
public class LensMetricQueryServiceImpl implements MetricQueryService {

    private static final String METRIC_VALUE_FIELD = "value";
    private static final String METRIC_NAME_FIELD = "metricName";

    @Value("${elasticsearch.jdbc.url}")
    private String esJDBCUrl;

    @Value("${elasticsearch.metric.index}")
    private String metricIndex;

    private ESQueryAdaptor esQueryAdaptor;

    @Autowired
    LensMetricsTagsService lensMetricsTagsService;

    @PostConstruct
    public void init() {
        try {
            Properties properties = new Properties();
            properties.put(PROP_URL, esJDBCUrl);
            properties.put(PROP_CONNECTIONPROPERTIES, "client.transport.ignore_cluster_name=true");
            esQueryAdaptor = new ESQueryAdaptor(properties);
            esQueryAdaptor.connect();
        } catch (Exception e) {
            log.error("error occurred.", e);
        }
    }

    @PreDestroy
    public void cleanup() {
        try {
            if (esQueryAdaptor != null) {
                esQueryAdaptor.close();
            }
        } catch (Exception e) {
            log.error("error occurred.", e);
        }
    }

    @Override
    public List<Map<String, Object>> query(LensMetricInVO lensMetricInVO, LensIntegrationDetailOutVO integrationDetailOutVO, MQLVariable... mqlVariables) {
        Map<String, Object> filters = new HashMap<>();
        if (!CollectionUtils.isEmpty(lensMetricInVO.getMetricFilter())) {
            for (String key : lensMetricInVO.getMetricFilter().keySet()) {
                String newKey = "tags." + key;
                filters.put(newKey, lensMetricInVO.getMetricFilter().get(key));
            }
        }
        filters.put(METRIC_NAME_FIELD, lensMetricInVO.getMetricName());

        List<String> groupBys = new ArrayList<>();
        if (!CollectionUtils.isEmpty(lensMetricInVO.getMetricBy())) {
            for (String field : lensMetricInVO.getMetricBy()) {
                String newKey = "tags." + field;
                groupBys.add(newKey);
            }
        }

        String sql = LensMQLBuilder.buildQueryDSL(METRIC_VALUE_FIELD,
                AggregatorEnum.valueOf(lensMetricInVO.getMetricAggr()).name(),
                metricIndex,
                MapUtils.merge(new HashMap<>(), lensMetricInVO.getScope(), filters),
                null,
                groupBys,
                new StartEndTimeRange(lensMetricInVO.getStartTime(), lensMetricInVO.getEndTime()),
                lensMetricInVO.getInterval(),
                lensMetricInVO.getGroupLimit());
        try {
            return esQueryAdaptor.query(sql, mqlVariables);
        } catch (Exception e) {
            log.error("error occurred. while query sql: " + sql, e);
            return null;
        }
    }

    @Override
    public List<String> getGroupByFields(LensIntegrationDetailOutVO integrationDetailOutVO, Long applicationId, String metricName) {
        Asserts.assertNotNull(applicationId, Constants.ErrorCode.PARAM_ERROR, "ApplicationId can not be null.");
        Asserts.assertNotEmpty(metricName, Constants.ErrorCode.PARAM_ERROR, "MetricName can not be empty.");
        List<String> groupByFields = lensMetricsTagsService.getMetricTagKeyByAppId(applicationId, metricName);
        if (!CollectionUtils.isEmpty(groupByFields)) {
            return groupByFields;
        }
        return Collections.emptyList();
    }

    @Override
    public List<LensMetricNamesOutVO> getMetricNames(LensIntegrationDetailOutVO integrationDetailOutVO, long applicationId) {
        List<String> metricNames = lensMetricsTagsService.getMetricNameByAppId(applicationId);
        if (!CollectionUtils.isEmpty(metricNames)) {
            return metricNames.stream().map(metricName -> {
                LensMetricNamesOutVO outVO = new LensMetricNamesOutVO();
                outVO.setName(metricName);
                return outVO;
            }).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    @Override
    public List<LensMonitorFilterOutVO> getFilters(LensIntegrationDetailOutVO integrationDetailOutVO, Long applicationId, String tierId, String metricName) {
        Asserts.assertNotNull(applicationId, Constants.ErrorCode.PARAM_ERROR, "applicationId can not be null.");
        Asserts.assertNotEmpty(metricName, Constants.ErrorCode.PARAM_ERROR, "metricName can not be empty.");

        List<String> filters = lensMetricsTagsService.getMetricTagKeyValueByAppId(applicationId, metricName);
        if (!CollectionUtils.isEmpty(filters)) {
            return filters.stream().map(filter -> {
                LensMonitorFilterOutVO outVO = new LensMonitorFilterOutVO();
                String[] keyValue = filter.split(Constants.COLON);
                Asserts.assertTrue(keyValue.length > 1, Constants.ErrorCode.DATA_NOT_EXIST);
                outVO.setId(filter);
                outVO.setType(keyValue[0]);
                outVO.setValue(keyValue[1]);
                outVO.setDesc(filter);
                return outVO;
            }).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }
}
